﻿using System;
using System.Text;
using System.Globalization;

using LeanEngine.Extensions;
using LeanEngine.Exception;

namespace LeanEngine.Validation
{
    /// <summary>
    /// Attribute for validate whether the value is between two comparison values.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public sealed class IsBetweenAttribute : ValidationAttribute
    {
        #region Constructor Members

        #region String Constructor Members

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.IsBetweenAttribute class with a date or numeric lower 
        /// and upper bound and an error message obtained from a resource.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ArgumentException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        /// <param name="lowerBound">An formatted string representing the date or numeric lower bound.</param>
        /// <param name="upperBound">An formatted string representing the date or numeric upper bound.</param>
        /// <param name="convertType">The type that the formatted string represents.</param>
        /// <remarks>
        ///     For dates you must use an ISO8601 formatted string like "2006-01-20T00:00:00".
        ///     For decimal values you must use an english formatted value like "1.99".
        /// </remarks>
        public IsBetweenAttribute(string lowerBound, string upperBound, ArgumentConvertType convertType, Type resourceType, string errorKey)
            : base(resourceType, errorKey)
        {
            this.LowerBound = ConvertArgument(lowerBound, convertType);
            this.UpperBound = ConvertArgument(upperBound, convertType);
        }

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.IsBetweenAttribute class with a date or numeric lower and and an error message.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ArgumentException"></exception>
        /// <param name="lowerBound">An formatted string representing the date or numeric lower bound.</param>
        /// <param name="upperBound">An formatted string representing the date or numeric upper bound.</param>
        /// <param name="convertType">The type that the formatted string represents.</param>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        /// <remarks>
        ///     For dates you must use an ISO8601 formatted string like "2006-01-20T00:00:00".
        ///     For decimal values you must use an english formatted value like "1.99".
        /// </remarks>
        public IsBetweenAttribute(string lowerBound, string upperBound, ArgumentConvertType convertType, string errorMessage)
            : base(errorMessage)
        {
            this.LowerBound = ConvertArgument(lowerBound, convertType);
            this.UpperBound = ConvertArgument(upperBound, convertType);
        }

        #endregion

        #region Byte Constructor Members

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.IsBetweenAttribute class with a lower and upper bound 
        /// and an error message obtained from a resource file.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        public IsBetweenAttribute(Byte lowerBound, Byte upperBound, Type resourceType, string errorKey)
            : base(resourceType, errorKey)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        /// <summary>
        /// Initialize a new instance with of LeanEngine.Validation.IsBetweenAttribute class a lower and upper bound and an error message.
        /// </summary>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        public IsBetweenAttribute(Byte lowerBound, Byte upperBound, string errorMessage)
            : base(errorMessage)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        #endregion

        #region Int16 Constructor Members

        /// <summary>
        /// Initialize a new instance with of LeanEngine.Validation.IsBetweenAttribute class a lower and upper bound and an 
        /// error message obtained from a resource file.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        public IsBetweenAttribute(Int16 lowerBound, Int16 upperBound, Type resourceType, string errorKey)
            : base(resourceType, errorKey)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        /// <summary>
        /// Initialize a new instance with of LeanEngine.Validation.IsBetweenAttribute class a lower and upper bound and an error message.
        /// </summary>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        public IsBetweenAttribute(Int16 lowerBound, Int16 upperBound, string errorMessage)
            : base(errorMessage)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        #endregion

        #region Int32 Constructor Members

        /// <summary>
        /// Initialize a new instance with of LeanEngine.Validation.IsBetweenAttribute class a lower and upper bound and 
        /// an error message obtained from a resource file.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        public IsBetweenAttribute(Int32 lowerBound, Int32 upperBound, Type resourceType, string errorKey)
            : base(resourceType, errorKey)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        /// <summary>
        /// Initialize a new instance with of LeanEngine.Validation.IsBetweenAttribute class a lower and upper bound and an error message.
        /// </summary>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        public IsBetweenAttribute(Int32 lowerBound, Int32 upperBound, string errorMessage)
            : base(errorMessage)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        #endregion

        #region Int64 Constructor Members

        /// <summary>
        /// Initialize a new instance with of LeanEngine.Validation.IsBetweenAttribute class a lower and upper bound 
        /// and an error message obtained from a resource file.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        public IsBetweenAttribute(Int64 lowerBound, Int64 upperBound, Type resourceType, string errorKey)
            : base(resourceType, errorKey)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        /// <summary>
        /// Initialize a new instance with of LeanEngine.Validation.IsBetweenAttribute class a lower and upper bound and an error message.
        /// </summary>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        public IsBetweenAttribute(Int64 lowerBound, Int64 upperBound, string errorMessage)
            : base(errorMessage)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        #endregion

        #region Decimal Constructor Members

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.IsBetweenAttribute class with a lower and upper bound and 
        /// an error message obtained from a resource file.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        public IsBetweenAttribute(Decimal lowerBound, Decimal upperBound, Type resourceType, string errorKey)
            : base(resourceType, errorKey)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.IsBetweenAttribute class with a lower and upper bound and an error message.
        /// </summary>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        public IsBetweenAttribute(Decimal lowerBound, Decimal upperBound, string errorMessage)
            : base(errorMessage)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        #endregion

        #region Datetime Constructor Members

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.IsBetweenAttribute class with a lower and upper bound and 
        /// an error message obtained from a resource file.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        public IsBetweenAttribute(DateTime lowerBound, DateTime upperBound, Type resourceType, string errorKey)
            : base(resourceType, errorKey)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.IsBetweenAttribute class with a lower and upper bound and an error message.
        /// </summary>
        /// <param name="lowerBound">A value representing the lower bound.</param>
        /// <param name="upperBound">A value representing the upper bound.</param>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        public IsBetweenAttribute(DateTime lowerBound, DateTime upperBound, string errorMessage)
            : base(errorMessage)
        {
            LowerBound = lowerBound;
            UpperBound = upperBound;
        }

        #endregion        

        #endregion

        #region PropertyInfo Members

        /// <summary>
        /// Gets or sets the lower comparison value.
        /// </summary>
        public object LowerBound { get; private set; }

        /// <summary>
        /// Gets or sets the higher comparison value.
        /// </summary>
        public object UpperBound { get; private set; }

        #endregion

        #region ValidationAttribute Members

        /// <summary>
        /// Validates the value according to the validation rule and throws an exception in case of validation failure.
        /// </summary>
        /// <exception cref="LeanEngine.Exception.BusinessException"></exception>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The value to be validated.</param>
        public override bool Validate(object value)
        {
            if (value.IsNull() || !value.IsBetween(this.LowerBound, this.UpperBound))
            {
                throw new BusinessException(this.ErrorMessage);
            }

            return true;
        }

        /// <summary>
        /// Validates the value according to the validation rule to the container and adds exception error in case of validation failure.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="value">The value to be validated.</param>
        /// <param name="errorMessageContainer">The container for all the error messages.</param>
        /// <exception cref="LeanEngine.Exception.BusinessException"></exception>
        public override void Validate(object value, StringBuilder errorMessageContainer)
        {
            if (value.IsNull() || !value.IsBetween(this.LowerBound, this.UpperBound))
            {
                errorMessageContainer.Append(this.ErrorMessage);
                errorMessageContainer.Append(Environment.NewLine);
            }
        }

        #endregion
    }
}
